using System;
using System.Xml;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Security.Cryptography.X509Certificates;


namespace Microsoft.ConnectionEngine.Common
{
    public enum CryptoTypes
    {
        encTypeNone = -1,
        encTypeDES = 0,
        encTypeRC2 = 1,
        encTypeRijndael = 2,
        encTypeTripleDES = 3
    }

    /// <summary>
    /// Defines a set of operations related to Encryption/Decryption.
    /// </summary>
    public class Crypto
    {

        /// <summary>
        /// Gets a X509 certificate, compatible with .NET 1.1 framework, based on arguments passed as parameters.
        /// </summary>
        /// <param name="certName">the Name of the certificate being retrieved.</param>
        /// <param name="storename">the name of the certificate store.</param>
        /// <param name="storelocation">the location of the certificate store.</param>
        /// <returns>Returns the retrieved certificate. If no certificate matches the description, returns null.</returns>
        public static X509Certificate GetX509Certificate(string certName, StoreName storename, StoreLocation storelocation)
        {
            X509Certificate2 cert = GetX509Certificate2(certName, storename, storelocation);
            return (cert == null)? null : (X509Certificate)cert;
        }

        /// <summary>
        /// Gets a X509 certificate, compatible with .NET 2.0 framework, based on arguments passed as parameters.
        /// </summary>
        /// <param name="certName">the Name of the certificate being retrieved.</param>
        /// <param name="storename">the name of the certificate store.</param>
        /// <param name="storelocation">the location of the certificate store.</param>
        /// <returns>Returns the retrieved certificate. If no certificate matches the description, returns null.</returns>
        public static X509Certificate2 GetX509Certificate2(string certName, StoreName storename, StoreLocation storelocation)
        {
            X509Store store = new X509Store(storename, storelocation);
            X509Certificate2 result = null;
            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection searchResult = store.Certificates.Find(X509FindType.FindBySubjectName, certName, false);

            if (searchResult.Count > 0)
                result = searchResult[0];

            return result;
        }

        private RSACryptoServiceProvider rsa;

        /// <summary>
        /// Loads the new Crypto object using an xml string that contains the key information.
        /// </summary>
        /// <param name="keyXml">A string representing the key information.</param>
        public Crypto(string keyXml)
        {
            CspParameters param = new CspParameters();
            param.Flags = CspProviderFlags.UseMachineKeyStore;
            rsa = new RSACryptoServiceProvider(param);
            rsa.FromXmlString(keyXml);
        }

        /// <summary>
        /// Loads the new Crypto object using an xml string or file that contains the key information
        /// </summary>
        /// <param name="keyXmlOrFilePath">a string representing either the key information or a path to a file containing the key information.</param>
        /// <param name="loadFromFile">A boolean set to true if keyXmlOrFilePath is a file path.</param>
        public Crypto(string keyXmlOrFilePath, bool loadFromFile)
        {
            CspParameters param = new CspParameters();
            param.Flags = CspProviderFlags.UseMachineKeyStore;
            rsa = new RSACryptoServiceProvider(param);

            if (loadFromFile)
            {
                TextReader reader = File.OpenText(keyXmlOrFilePath);
                rsa.FromXmlString(reader.ReadToEnd());
                reader.Close();
            }
            else
            {
                rsa.FromXmlString(keyXmlOrFilePath);
            }
        }


        private System.Security.Cryptography.SymmetricAlgorithm GetAlgorythmObject(CryptoTypes type)
        {
            SymmetricAlgorithm crypto;
            switch (type)
            {
                case CryptoTypes.encTypeDES:
                    crypto = new DESCryptoServiceProvider();
                    break;
                case CryptoTypes.encTypeRC2:
                    crypto = new RC2CryptoServiceProvider();
                    break;
                case CryptoTypes.encTypeRijndael:
                    crypto = new RijndaelManaged();
                    break;
                case CryptoTypes.encTypeTripleDES:
                    crypto = new TripleDESCryptoServiceProvider();
                    break;
                default:
                    crypto = new RijndaelManaged();
                    break;
            }
            return crypto;
        }

        /// <summary>
        /// Encrypts a string of data using the Encryption type passed as a parameter.
        /// </summary>
        /// <param name="data">The string representing the data to be encrypted.</param>
        /// <param name="type">The type of encryption to be used.</param>
        /// <returns>Returns an object representing the encrypted data.</returns>
        public EncryptedDataInfo Encrypt(string data, CryptoTypes type)
        {
            string encIV;
            string encKey;
            string encData;

            ICryptoTransform encryptor;
            CryptoStream cStream;
            MemoryStream mStream = new MemoryStream();

            try
            {
                SymmetricAlgorithm des = GetAlgorythmObject(type);
                des.GenerateIV();
                des.GenerateKey();

                encIV = Convert.ToBase64String(rsa.Encrypt(des.IV, false));
                encKey = Convert.ToBase64String(rsa.Encrypt(des.Key, false));

                encryptor = des.CreateEncryptor(des.Key, des.IV);
                cStream = new CryptoStream(mStream, encryptor, CryptoStreamMode.Write);

                byte[] bytesIn = Encoding.ASCII.GetBytes(data);

                cStream.Write(bytesIn, 0, bytesIn.Length);
                cStream.FlushFinalBlock();
                cStream.Close();

                byte[] bytesOut = mStream.ToArray();
                mStream.Close();

                encData = Convert.ToBase64String(bytesOut);

                return new EncryptedDataInfo(encData, encIV, encKey);
            }
            catch (Exception ex)
            {
                mStream.Close();
                throw ex;
            }
        }


        /// <summary>
        /// Decrypts data from a xml representation of an EncryptedDataInfo object
        /// </summary>
        /// <param name="encryptedDataInfoXml">An xml object representing the encrypted data.</param>
        /// <param name="type">The type of encryption used.</param>
        /// <returns>Returns a string representing the decrypted data.</returns>
        public string Decrypt(string encryptedDataInfoXml, CryptoTypes type)
        {
            return Decrypt(new EncryptedDataInfo(encryptedDataInfoXml), type);
        }

        /// <summary>
        /// Decrypts data from an EncryptedDataInfo object.
        /// </summary>
        /// <param name="encryptedDataInfo">An object representing the encrypted data.</param>
        /// <param name="type">The type of encryption used.</param>
        /// <returns>Returns a string representing the decrypted data.</returns>
        public string Decrypt(EncryptedDataInfo encryptedDataInfo, CryptoTypes type)
        {
            return Decrypt(encryptedDataInfo.EncryptedData, encryptedDataInfo.EncryptedKey, encryptedDataInfo.EncryptedIV, type);
        }

        /// <summary>
        /// Decrypts the Key and IV using RSA and then decrypts the data using the type of encryption passed as a parameter.
        /// </summary>
        /// <param name="encryptedData">The encrypted data.</param>
        /// <param name="encryptedKey">The encrypted key that will be used for decryption.</param>
        /// <param name="encryptedIV">The initalization vector used for encryption.</param>
        /// <param name="type">The type of encryption used.</param>
        /// <returns>Returns a string representing the decrypted data.</returns>
        public string Decrypt(string encryptedData, string encryptedKey, string encryptedIV, CryptoTypes type)
        {
            string returnValue;

            byte[] iv = rsa.Decrypt(Convert.FromBase64String(encryptedIV), false);
            byte[] key = rsa.Decrypt(Convert.FromBase64String(encryptedKey), false);
            byte[] data = Convert.FromBase64String(encryptedData);

            MemoryStream mStream = new MemoryStream(data);

            SymmetricAlgorithm des = GetAlgorythmObject(type);
            ICryptoTransform decryptor = des.CreateDecryptor(key, iv);

            CryptoStream cStream = new CryptoStream(mStream, decryptor, CryptoStreamMode.Read);
            StreamReader reader = new StreamReader(cStream);

            returnValue = reader.ReadToEnd();

            reader.Close();
            mStream.Close();
            cStream.Close();

            return returnValue;
        } // Crypto


        /// <summary>
        /// Container for encrypted data
        /// </summary>
        public class EncryptedDataInfo
        {
            private string encData;
            private string encIV;
            private string encKey;

            /// <summary>
            /// Instantiates a EncryptedDataInfo object based on the arguments passed as a parameter.
            /// </summary>
            /// <param name="encryptedData">The encrypted data.</param>
            /// <param name="encryptedIV">The initalization vector used for encryption.</param>
            /// <param name="encryptedKey">The encrypted key that will be used for decryption.</param>
            public EncryptedDataInfo(string encryptedData, string encryptedIV, string encryptedKey)
            {
                encData = encryptedData;
                encIV = encryptedIV;
                encKey = encryptedKey;
            }

            /// <summary>
            /// Instantiates a EncryptedDataInfo object based on the arguments passed as a parameter.
            /// </summary>
            /// <param name="encryptedDataInfoXml">A string representing a serialization of the EncryptedDataInfo object.</param>
            public EncryptedDataInfo(string encryptedDataInfoXml)
            {
                XmlDocument xml = new XmlDocument();
                xml.LoadXml(encryptedDataInfoXml);

                encData = xml.SelectSingleNode(@"/EncryptedDataInfo/EncryptedData").InnerText;
                encIV = xml.SelectSingleNode(@"/EncryptedDataInfo/EncryptedIV").InnerText;
                encKey = xml.SelectSingleNode(@"/EncryptedDataInfo/EncryptedKey").InnerText;
            }

            /// <summary>
            /// Convert the EncryptedDataInfo object to a XML string.
            /// </summary>
            /// <returns>Returns a string representing the serialized EncryptedDataInfo.</returns>
            public string ToXml()
            {
                XmlDocument xml = new XmlDocument();
                XmlElement root = xml.CreateElement("EncryptedDataInfo");
                XmlElement data = xml.CreateElement("EncryptedData");
                XmlElement iv = xml.CreateElement("EncryptedIV");
                XmlElement key = xml.CreateElement("EncryptedKey");

                data.InnerText = encData;
                iv.InnerText = encIV;
                key.InnerText = encKey;

                root.AppendChild((XmlNode)data);
                root.AppendChild((XmlNode)iv);
                root.AppendChild((XmlNode)key);
                xml.AppendChild((XmlNode)root);

                return xml.OuterXml;
            }

            /// <summary>
            /// Convert the EncryptedDataInfo object to a XML string.
            /// </summary>
            /// <returns>Returns a string representing the serialized EncryptedDataInfo.</returns>
            public override string ToString()
            {
                return ToXml();
            }

            /// <summary>
            /// The encrypted data.
            /// </summary>
            public string EncryptedData { get { return encData; } }
            /// <summary>
            /// The initalization vector used for encryption.
            /// </summary>
            public string EncryptedIV { get { return encIV; } }
            /// <summary>
            /// The encrypted key that will be used for decryption.
            /// </summary>
            public string EncryptedKey { get { return encKey; } }
        }


        /// <summary>
        /// Used to create and save new keys
        /// </summary>
        public class RSAKeyCreator
        {
            private string publicKeyXml;
            private string privateKeyXml;

            public RSAKeyCreator()
            {
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024);
                publicKeyXml = rsa.ToXmlString(false);
                privateKeyXml = rsa.ToXmlString(true);
            }

            public RSAKeyCreator(int keySize)
            {
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(keySize);
                publicKeyXml = rsa.ToXmlString(false);
                privateKeyXml = rsa.ToXmlString(true);
            }

            public void SavePublicKeyToFile(string filePath)
            {
                TextWriter writer = File.CreateText(filePath);
                writer.Write(publicKeyXml);
                writer.Flush();
                writer.Close();
            }

            public void SavePrivateKeyToFile(string filePath)
            {
                TextWriter writer = File.CreateText(filePath);
                writer.Write(privateKeyXml);
                writer.Flush();
                writer.Close();
            }

            public string PublicKeyXml { get { return publicKeyXml; } }
            public string PrivateKeyXml { get { return privateKeyXml; } }
        } // RSAKeyCreator

        public static CryptoTypes GetCryptoType(string encryptionalgorithm)
        {

            //Work Item ID: 62
            //Modified By: SachinJo
            //Avoiding ToLower call.
            if(string.Compare(encryptionalgorithm, "des", true) == 0)
                return Common.CryptoTypes.encTypeDES;
            else if (string.Compare(encryptionalgorithm, "rcs", true) == 0)
                return Common.CryptoTypes.encTypeRC2;
            else if (string.Compare(encryptionalgorithm, "rijndael", true) == 0)
                return Common.CryptoTypes.encTypeRijndael;
            else if (string.Compare(encryptionalgorithm, "tripledes", true) == 0)
                return Common.CryptoTypes.encTypeTripleDES;
            else
                return Common.CryptoTypes.encTypeTripleDES;



            //switch (encryptionalgorithm.ToLower())
            //{
            //    case "des":
            //        return Common.CryptoTypes.encTypeDES;
            //    case "rcs":
            //        return Common.CryptoTypes.encTypeRC2;
            //    case "rijndael":
            //        return Common.CryptoTypes.encTypeRijndael;
            //    case "tripledes":
            //        return Common.CryptoTypes.encTypeTripleDES;
            //    default:
            //        return Common.CryptoTypes.encTypeTripleDES;
            //}

        }
    }

}
